package xyz.app;

import jakarta.persistence.EntityGraph;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Subgraph;
import jakarta.persistence.metamodel.SingularAttribute;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import xyz.app.dependencies.HasDomainMembers;
import xyz.app.dependencies.OwnerEntry;

import java.util.*;

@Slf4j
@Service @Lazy
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class DaoRegistry {

    @Getter
    private final EntityManagerFactory emf;

    private final Map<Class<? extends AbstractDomain>, Dao<?>> daos = new HashMap<>();

    public void addDao(@NonNull Dao<?> dao) {
        daos.putIfAbsent(dao.domainClass(), dao);
    }

    @SuppressWarnings("unchecked")
    public <T extends AbstractDomain> Optional<Dao<T>> getDao(@NonNull Class<T> domainClass) {
        return Optional.ofNullable((Dao<T>) daos.get(domainClass));
    }

    public <T extends AbstractDomain, V extends AbstractDomain> Optional<OwnerEntry<T, V>> ownerEntry(@NonNull SingularAttribute<? super T, V> attribute, Class<T> ownerClass) {
        return getDao(ownerClass).map(dao -> OwnerEntry.of(attribute, dao));
    }

    public <T extends AbstractDomain, V extends AbstractDomain> Optional<OwnerEntry<T, V>> ownerEntryRemovable(@NonNull SingularAttribute<? super T, V> attribute, Class<T> ownerClass) {
        return getDao(ownerClass).map(dao -> OwnerEntry.removable(attribute, dao));
    }

    public void configureGraph(Collection<String> nodes, Object rawGraph) {
        nodes.stream()
                .map(attribute -> {
                    if (rawGraph instanceof EntityGraph<?> graph) return graph.addSubgraph(attribute);
                    if (rawGraph instanceof Subgraph<?> graph) return graph.addSubgraph(attribute);
                    return null;
                })
                .filter(Objects::nonNull)
                .filter(subgraph -> AbstractDomain.class.isAssignableFrom(subgraph.getClassType()))
                .forEach(subgraph -> {
                    if (daos.get(subgraph.getClassType()) instanceof HasDomainMembers<?> hasDomainMembers)
                        hasDomainMembers.configureGraph(subgraph);
                });
    }
}